/*
	system_error.c

	contains functionality for handling exceptions and system errors

	Author: Aidan Goddard 15/12/13
*/

#include"headers/CPU_exception.h"
#include"headers/system_error.h"

#include<stdarg.h>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// internal low-level functions

// write an unsigned integer
static int printiu(int char_offset, uint64_t value, int base)
{
	// define a buffer pointer
	char *buff = (char*)(KOFFSET + 0xb8000);

	// check if 0
	if(value == 0)
	{
		buff[char_offset] = '0';
		return char_offset + 2;
	}

	// numbers array and string array
	char *numbers = "0123456789ABCDEF";
	char number[65];
	number[64] = 0x00;

	// some values
	uint64_t quotient = value;
	uint64_t remainder = 0;
	int i = 63;

	// get each character
	while(1)
	{
		// get quotient and remainder
		remainder = quotient % base;
		quotient = quotient / base;

		// check if finished
		if(!(remainder || quotient))
		{
			break;
		}

		// get the character
		number[i--] = numbers[remainder];

	}

	// print the string
	while(number[++i] != 0x00)
	{
		buff[char_offset] = number[i];
		char_offset += 2;
	}

	// finished
	return char_offset;
}

// print a signed integer
static int printi(int char_offset, int64_t value)
{
	// define a buffer pointer
	char *buff = (char*)(KOFFSET + 0xb8000);

	// check if value is negative
	if(value < 0)
	{
		// print negative sign
		buff[char_offset] = '-';
		char_offset += 2;

		// multiply value by -1
		value = value * -1;
	}

	// print the number in base 10
	char_offset = printiu(char_offset, value, 10);

	// finished
	return char_offset;
}

// convert an address to a string (address has leading zeros included)
static char tmp_address[17];	// 16 chars with trailing nullchar

static char *address_to_string(uint64_t address)
{
	// number chars array
	char *numbers = "0123456789ABCDEF";

	// first setup string
	int i;
	for(i = 0; i < (sizeof(uint64_t) * 2); i++)
	{
		tmp_address[i] = '0';
	}

	// get start position (start at lowest significant portion)
	int k = sizeof(uint64_t) * 2;
	tmp_address[k] = 0x00;		// set nullchar

	// loop through, placing chars
	for(i = 0; i < (sizeof(uint64_t) * 2); i++)
	{
		// point to next char to fill
		k--;

		// get value for this nibble (4 bits)
		uint64_t value = (address >> (i * 4)) & 0x0f;

		// set the char
		tmp_address[k] = numbers[value];
	}

	return tmp_address;
}

// function to generate a string and write it directly to the buffer
static int WriteStringToBuffer(int char_offset, char *string, ...)
{
	// define a buffer pointer
	char *buff = (char*)(KOFFSET + 0xb8000);

	// sort out variable arguments list
	va_list va;
	va_start(va, string);

	int i;
	for(i = 0; string[i] != 0x00; i++)
	{
		if(string[i] == '%')
		{
			i++;
			if(string[i] == '%')
			{
				buff[char_offset] = '%';
				char_offset += 2;
			}
			else if(string[i] == 'c')
			{
				buff[char_offset] = (char)va_arg(va, int);
				char_offset += 2;
			}
			else if(string[i] == 'd' || string[i] == 'i')
			{
				char_offset = printi(char_offset, va_arg(va, int64_t));
			}
			else if(string[i] == 'o')
			{
				char_offset = printiu(char_offset, va_arg(va, uint64_t), 8);
			}
			else if(string[i] == 'u')
			{
				char_offset = printiu(char_offset, va_arg(va, uint64_t), 10);
			}
			else if(string[i] == 'x')
			{
				char_offset = printiu(char_offset, va_arg(va, uint64_t), 16);
			}
			else if(string[i] == 'b')
			{
				char_offset = printiu(char_offset, va_arg(va, uint64_t), 2);
			}
			else if(string[i] == 's')
			{
				char *the_string = (char*)va_arg(va, uint64_t);
				int k;

				for(k = 0; the_string[k] != 0x00; k++)
				{
					buff[char_offset] = the_string[k];
					char_offset += 2;
				}
			}
			else if(string[i] == 'a')
			{
				uint64_t address = va_arg(va, uint64_t);
				char *address_string = address_to_string(address);
				int k;

				for(k = 0; address_string[k] != 0x00; k++)
				{
					buff[char_offset] = address_string[k];
					char_offset += 2;
				}
			}
			else if(string[i] == 'f')
			{
				double value = va_arg(va, double);

				// print non-fraction part and decimal point
				char_offset = WriteStringToBuffer(char_offset, "%i.", (int64_t)value);
				if(value < 0)
				{
					value = -value;
				}

				// print fraction part
				value = value - (int)value;
				int k;

				for(k = 0; k < 10; k++)
				{
					value = value * 10;
					if(((int)value) == 0)
					{
						buff[char_offset] = '0';
						char_offset += 2;
					}

					int test = (value - (int)value) * 1000000000;
					if(test == 0)
					{
						break;
					}
				}

				char_offset = WriteStringToBuffer(char_offset, "%i", (int)value);


			}

			else
			{
				continue;
			}
		}
		else
		{
			buff[char_offset] = string[i];
			char_offset += 2;
		}
	}

	va_end(va);

	// finished
	return char_offset;
}


// function to disable the scheduler
// assumes interrupts are disabled
static void DisableScheduler(void)
{
	// get the CPU number of this processor
	uint32_t CPUID = GetCPUID();

	// get the total number of operating processors
	uint32_t CPU_count = SCHEDULER->active_CPU_count;

	// set scheduling state to disabled
	SCHEDULER->scheduling_state = SCHEDULING_STATE_DISABLED;

	// wait until all other cpus have reported as not scheduling
	int i;
	for(i = 0; i < CPU_count; i++)
	{
		if(i != CPUID)
		{
			while(SCHEDULER->CPU_current_state[i] != CPU_NOT_SCHEDULING){}
		}
	}

	// finished
	return;
}

static void ClearScreen(void)
{
	// get pointer to screen buffer
	uint16_t *screen = (uint16_t*)(KOFFSET + 0xb8000);

	// clear it
	int i;
	for(i = 0; i < 2000; i++)
	{
		screen[i] = 0x1f00;
	}

	// finished
	return;
}

static void PrintTitle(const char *s_title)
{
	// print the title starting 2 chars from the left on the second line
	char *buff = (char*)(KOFFSET + 0xb8000);
	int i, k;
	for(i = 0, k = 164; s_title[i] != 0; i++, k += 2)
	{
		buff[k] = s_title[i];
	}

	// finished
	return;
}

static void PrintModule(const char *s_module)
{
	// print "modle: MODNAME"
	const char *mod = "IN MODULE: \"";
	const char *mod2 = "\"";

	char *buff = (char*)(KOFFSET + 0xb8000);
	int i, k;
	for(i = 0, k = (160 * 4) + 4; i < 12; i++, k += 2)
	{
		buff[k] = mod[i];
	}

	for(i = 0; s_module[i] != 0; i++, k += 2)
	{
		buff[k] = s_module[i];
	}

	for(i = 0; i < 1; i++)
	{
		buff[k] = mod2[i];
	}

	// finished
	return;


}

static void PrintFunction(const char *s_function)
{
	const char *mod = "IN FUNCTION: \"";
	const char *mod2 = "\"";

	char *buff = (char*)(KOFFSET + 0xb8000);
	int i, k;
	for(i = 0, k = (160 * 5) + 4; i < 14; i++, k += 2)
	{
		buff[k] = mod[i];
	}

	for(i = 0; s_function[i] != 0; i++, k += 2)
	{
		buff[k] = s_function[i];
	}

	for(i = 0; i < 1; i++)
	{
		buff[k] = mod2[i];
	}

	// finished
	return;
}

static void PrintMessage(const char *s_message)
{
	const char *mod = "WITH MESSAGE: \"";
	const char *mod2 = "\"";

	char *buff = (char*)(KOFFSET + 0xb8000);
	int i, k;
	for(i = 0, k = (160 * 22) + 4; i < 15; i++, k += 2)
	{
		buff[k] = mod[i];
	}

	for(i = 0; s_message[i] != 0; i++, k += 2)
	{
		buff[k] = s_message[i];
	}

	for(i = 0; i < 1; i++)
	{
		buff[k] = mod2[i];
	}

	// finished
	return;
}




////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// top-level functions

// function to signal an internal kernel error
void KERNEL_ERROR(const char *module, const char *s_function, const char *message)
{
	// disabled interrupts
	__asm__ __volatile__ ("":::"memory");
	__asm__ __volatile__ ("cli");
	__asm__ __volatile__ ("":::"memory");

	// disable scheduling
	DisableScheduler();

	// clear the screen
	ClearScreen();

	// print stuff
	PrintTitle("KERNEL ERROR SIGNALED");
	PrintModule(module);
	PrintFunction(s_function);
	PrintMessage(message);

	// stop
	__asm__ __volatile__ ("":::"memory");
	__asm__ __volatile__ ("hlt");
	__asm__ __volatile__ ("":::"memory");

	return;
}

// function to signal a fatal system exception
void KERNEL_EXCEPTION(struct REGISTER_STATE *regs)
{
	// disabled interrupts
	__asm__ __volatile__ ("":::"memory");
	__asm__ __volatile__ ("cli");
	__asm__ __volatile__ ("":::"memory");

	// disable scheduling
	DisableScheduler();

	// clear the screen
	ClearScreen();

	// exception strings array
	const char exceptions[32][30] = {
	"Divide By Zero",
	"Debug",
	"NMI",
	"Breakpoint",
	"Overflow",
	"Bound Range",
	"Invalid Opcode",
	"Device Not Available",
	"Double Fault",
	"",
	"Invalid TSS",
	"Segment Not Present",
	"Stack",
	"General Protection",
	"Page Fault",
	"",
	"x87 FPU Exception",
	"Alignment Check",
	"Machine Check",
	"SIMD Floating Point Exception",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"Security Exception",
	""};

	// print info to the screen
	WriteStringToBuffer(162, "KERNEL EXCEPTION SIGNALED %u (%s)", regs->EXCEPTION, exceptions[regs->EXCEPTION]);

	WriteStringToBuffer(160 * 3, " REGISTER DUMP:");

	WriteStringToBuffer(160 * 5, " CR2: 0x%a ERROR CODE: 0x%a", regs->CR2, regs->ERROR_CODE);
	WriteStringToBuffer(160 * 6, " CR0: 0x%a CR3: 0x%a CR4 0x%a", regs->CR0, regs->CR3, regs->CR4);

	WriteStringToBuffer(160 * 8 , " RIP: 0x%a RSP: 0x%a", regs->RIP, regs->RSP);
	WriteStringToBuffer(160 * 9 , " CS : 0x%a SS : 0x%a", regs->CS, regs->SS);

	WriteStringToBuffer(160 * 11, " R8 : 0x%a RAX: 0x%a", regs->R8, regs->RAX);
	WriteStringToBuffer(160 * 12, " R9 : 0x%a RBX: 0x%a", regs->R9, regs->RBX);
	WriteStringToBuffer(160 * 13, " R10: 0x%a RCX: 0x%a", regs->R10, regs->RCX);
	WriteStringToBuffer(160 * 14, " R11: 0x%a RDX: 0x%a", regs->R11, regs->RDX);
	WriteStringToBuffer(160 * 15, " R12: 0x%a RDI: 0x%a", regs->R12, regs->RDI);
	WriteStringToBuffer(160 * 16, " R13: 0x%a RSI: 0x%a", regs->R13, regs->RSI);
	WriteStringToBuffer(160 * 17, " R14: 0x%a", regs->R14);
	WriteStringToBuffer(160 * 18, " R15: 0x%a", regs->R15);

	WriteStringToBuffer(160 * 20, " RFLAGS: 0x%a", regs->RFLAGS);

	// stop
	__asm__ __volatile__ ("":::"memory");
	__asm__ __volatile__ ("hlt");
	__asm__ __volatile__ ("":::"memory");
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ISR handlers
